/* tripl/arch/arch_templ.c 
 * 
 * This file is part of tripl. 
 * 
 * tripl is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * tripl is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with tripl. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <fcntl.h>

#include <errno.h>

#include <klist.h>
#include <config.h>

#include <tripl/tripl.h>
#include <tripl/limits.h>
#include <tripl/arch.h>



char 	*psave;
struct klist_node names_list = KLIST_NODE_INIT(&names_list);

extern char *line_buffer;
extern char *line_eof;



__arch_templ static int new_var(char *p, struct arch *);
__arch_templ static int set_name(char *p, struct arch *, long *out);
__arch_templ static int math_set_name(char *p, struct arch *, long *out);
__arch_templ static int declare_object(char *p, struct arch *);
__arch_templ static int interp_in_interp(char *p, struct arch *, long *out);

__arch_templ int cstyle_declare(char *p, struct arch *);
__arch_templ union math_expr *parse_math_expr(char *p);


__arch_templ int arch_do_keyword(char *p, struct arch *arch_fn) {
	long ignored;

	psave = p;

	if (unlikely(p == line_eof)) 
		return 0;

	switch (*p) {
	case '$':
		/* '$ asdf = 0;' 	--- asdf = 0
		 * '$ asdf 0;'		--- asdf = 0
		 * '$ asdf;'		--- asdf = 0
		 * */

		p++;
		_skip_spaces(p);

		new_var(p, arch_fn);

		break;

	case '!':
		p++;
		_skip_spaces(p);

		if (likely(!arch_fn->arch_dump)) 
			exit(generic_error("unsupported dumping in current mode", psave, lno));

		arch_fn->arch_dump(getattrmask(p, dump_attrtab));

		break;

	case '<':
		declare_object(p, arch_fn);

		break;

	case '`':
		/* running code in another interpreter */

		interp_in_interp(p, arch_fn, &ignored);

		break;

	case '+':
	case '-':
		if (!math_set_name(p, arch_fn, &ignored)) 
			exit(generic_error("unknow keyword", psave, lno));

		break;

	default:
		if (unlikely(!set_name(p, arch_fn, &ignored) && 
					!math_set_name(p, arch_fn, &ignored) && 
					!cstyle_declare(p, arch_fn))) 
			exit(generic_error("unknow keyword", psave, lno));

		break;
	}

	return 0;
}



struct klist_node *find_name(char *name) {
	struct klist_node *n;

	for (n = names_list.next; n != &names_list; n = n->next) 
		if (!strcmp(n->payload, name)) 
			return n;

	return NULL;
}


__arch_templ static int new_var(char *p, struct arch *arch_fn) {
	char *name;
	int type, out, attr, comma = 0;
	struct klist_node *t;
	
	while (getword(p, &type, &out)) {
		if (unlikely(type != WORDTYPE_NAME)) 
			exit(generic_error("a name for variable required", psave, lno));

		name = strndup(p, out);

		p += out;
		_skip_spaces(p);

		getword(p, &type, &out);

		switch (type) {
		case WORDTYPE_KWORD:
			if (out == ',') {
				comma = 1;

				p++;
				_skip_spaces(p);

				break;
			}

			if (out == '@') {
				out = 0;

				break;
			}

			if (unlikely(out != '=' )) 
				exit(generic_error("keyword but it's not a '='", psave, lno));
		
			++p;
			_skip_spaces(p);

			getword(p, &type, &out);

			if (unlikely(type != WORDTYPE_NR)) 
				exit(generic_error("a number required", psave, lno));

			_skip_word(p);

			break;

		case WORDTYPE_NR:
			/* to attribute (if have) */

			_skip_word(p);

			break;

		case 0:
			out = 0;

			break;

		default:
			exit(generic_error("bad word after variable name", psave, lno));
		}


		if (!comma) 
			attr = getattrmask(p, vardef_attrtab);

		if (unlikely((t = find_name(name)) && !(t->xmask & XMASK_WEAK))) {
			error(name);

			exit(generic_error("name already defined", psave, lno));
		}

		arch_fn->arch_new_var(name, out, attr);

		if (!comma) 
			break;

		comma = 0;
		_skip_spaces(p);
	}

	return 0;
}

static int getrightvalue(char *p, struct arch *arch_fn) {
	long res;

	if (*p == '`') {
		if (!interp_in_interp(p, arch_fn, &res)) 
			/* actually, it never be here */
			exit(generic_error("invalid right-value", psave, lno));
	}

	else if (!set_name(p, arch_fn, &res)  
			&& !math_set_name(p, arch_fn, &res)) 
		exit(generic_error("invalid right-value", psave, lno));

	return res;
}


static int set_name(char *p, struct arch *arch_fn, long *res) {
	char *name;
	int type, out;

	getword(p, &type, &out);

	if (type != WORDTYPE_NAME) 
		return 0;

	name = strndup(p, out);

	p += out;
	_skip_spaces(p);

	getword(p, &type, &out);

	if (type != WORDTYPE_KWORD || out != '=') 
		return 0;

	p++;
	_skip_spaces(p);

	getword(p, &type, &out);

	if (type == WORDTYPE_KWORD && out == '$') {
		out = arch_fn->arch_math_expr(p, name);

		goto skip;
	}

	else if (type != WORDTYPE_NR)
		out = getrightvalue(p, arch_fn);

	arch_fn->arch_set_var(name, out, 0);

skip:	*res = out;

	free(name);

	return 1;
}




/* 9 -> a; and warning that math_set_name doesn't support instructions 
 * such as 'a = 0 -> b' */

static int math_set_name(char *p, struct arch *arch_fn, long *res) {
	char *name;
	int type, out, nr;

	getword(p, &type, &out);

	if (type != WORDTYPE_NR) 
		return 0;

	nr = out;

	/* this requires a space */
	_skip_word(p);
	getword(p, &type, &out);

	if (unlikely(type != WORDTYPE_KWORD || out != '-')) 
		exit(generic_error("a '->' required", psave, lno));

	p++;
	getword(p, &type, &out);

	if (unlikely(type != WORDTYPE_KWORD || out != '>')) 
		exit(generic_error("a '->' required", psave, lno));

	p++;
	_skip_spaces(p);

	getword(p, &type, &out);

	if (unlikely(type != WORDTYPE_NAME)) 
		exit(generic_error("a name required", psave, lno));

	name = strndup(p, out);

	arch_fn->arch_set_var(name, nr, 0);
	*res = nr;

	free(name);

	return 1;
}



/*  
 *  1. 	
 *  	< "libfoo.a" 	@ dynalib;
 *  2. 
 *  	< fn 		@ fn;
 *
 *  3. 
 *  	< fn 2 		@ fn;
 *
 *  4. 	
 *  	< fn (a, b) 	@ fn;
 *
 *  5. 
 *  	< age 		@ var;
 * */

static int declare_object(char *p, struct arch *arch_fn) {
	char *name;
	int type, out, attr;

	enum { SHARED, NAMES, FUNCTION } guess;
	struct klist_node *n, *t;

	/* skip '<' */

	p++;
	_skip_spaces(p);

	
	if (unlikely(!getword(p, &type, &out))) 
		exit(generic_error("a module name or object name required", psave, lno));


	switch (type) {
	case WORDTYPE_STR:
		/* it must be a dynamic library */

		name = strndup(p + 1, out);
		guess = SHARED;

		if (arch_fn->arch_import_shared) 
			arch_fn->arch_import_shared(name);

		p += out + 2;

		/* we will not free the name because interp_libs_dump uses it */

		return 0;

	case WORDTYPE_NAME:
		/* functions or variables */

		name = strndup(p, out);

		/* join to the name list */
		
		n = malloc(sizeof(struct klist_node));
		klist_init(n);

		n->payload = name;

		guess = NAMES;

		p += out;

		break;

	default:
		exit(generic_error("invalid type for '<'", psave, lno));
	}

	_skip_spaces(p);

	/* try to getword */

	getword(p, &type, &out);
	
	if (type == WORDTYPE_KWORD && out == '@') 
		goto skip;

	/* can just be a function define */

	guess = FUNCTION;

	if (type == WORDTYPE_NR) {
		/* only number of arguments */
		if (!out) 
			/* special case */
			n->value = DECLARE_NOARG;

		else 
			n->value = out;

		_skip_word(p);
		_skip_spaces(p);

		goto skip;
	}

	/* argument list */

	if (type == WORDTYPE_KWORD && out == '(') {
		n->value = 0;

		p++;
		_skip_spaces(p);

		for (;;) {
			if (unlikely(!getword(p, &type, &out))) 
				/* end but no ')' */
				exit(generic_error("argument list terminated with no ')'", psave, lno));


			switch (type) {
			case WORDTYPE_KWORD:
				if (unlikely(out != ')' && out != ',')) 
					exit(generic_error("invalid operators between arguments", psave, lno));

				if (out == ')') {
					if (!n->value) 
						n->value = DECLARE_NOARG;

					p++;
					_skip_spaces(p);

					goto skip;
				}

				p++;
				_skip_spaces(p);

				break;

			case WORDTYPE_NAME:
				n->value++;

				p += out;
				_skip_spaces(p);

				break;

			default:
				exit(generic_error("invalid type between arguments", psave, lno));
			}
		}
	}


	if (!n->value) 
		n->value = DECLARE_NOARG;


skip:	attr = getattrmask(p, declare_attrtab);

	if (unlikely(!attr && guess != SHARED)) 
		exit(generic_error("you must give attributes for declares", psave, lno));

	if (attr & 2) {
		n->value |= DECLARE_FN;
		n->xmask = XMASK_FN;
	}

	else if (attr & 4) {
		if (unlikely(guess == FUNCTION)) 
			exit(generic_error("you gave us a function declare but told us this is a variable declare? ", 
						psave, lno));

		/* variable */
		n->value = DECLARE_VAR;
	}

	else 
		exit(generic_error("one of 'fn' or 'var' must be gave", psave, lno));


	if (unlikely((t = find_name(n->payload))) && !(t->xmask & XMASK_WEAK)) {
		error((char *) n->payload);

		exit(generic_error("name already defined", psave, lno));
	}

	if (t && t->xmask == XMASK_WEAK) 
		if (((t->value & DECLARE_FN) && !(n->value & DECLARE_FN)) || 
				((n->value & DECLARE_FN) && !(t->value & DECLARE_FN))) {
			error((char *) n->payload);

			exit(generic_error("name defined in different type", psave, lno));
		}
	

	n->xmask |= XMASK_WEAK;
	klist_add(&names_list, n);

	return 0;
}


static int iini_run(char *interp_path, char *code) {
	int pid, stat;

	/* we use the command-line '-c' */

	fprintf(stderr, "\nRun %s as interpreter-in-interpreter: \n", interp_path);

	if (!(pid = fork())) {
		/* child */

#if CONFIG_NODISPLAY_IINI

		int fd = open("/dev/null", O_WRONLY);

		dup2(fd, 1);
		dup2(fd, 2);
#endif

		execl(interp_path, interp_path, "-c", code, NULL);

		error("execl: %s\n", strerror(errno));

		exit(generic_error("interp-in-interp failed", psave, lno));
	}

	waitpid(pid, &stat, 0);

	if (WIFEXITED(stat)) 
		fprintf(stderr, "\n%s exited normally with status %d\n", interp_path, WEXITSTATUS(stat));

	else if (WIFSIGNALED(stat)) {
		error("%s terminated by signal %d%s\n", interp_path, WTERMSIG(stat), 

#ifdef WCOREDUMP
				WCOREDUMP(stat) ? " (core dumped)" : ""
#else
				""
#endif
		);

		exit(generic_error("interp-in-interp failed", psave, lno));
	}

	return WEXITSTATUS(stat);
}




static int interp_in_interp(char *p, struct arch *arch_fn, long *res) {
	int type, out;
	char *interp, *code, *end;

	/* first get the interpreter name */

	p++;
	_skip_spaces(p);

	if (!getword(p, &type, &out)) 
		exit(generic_error("interpreter name required", psave, lno));


	interp = strndup(p + 1, out);

	p += out + 2;
	_skip_spaces(p);

	
	if (!getword(p, &type, &out) || out != '{') 
		exit(generic_error("start mark '{' required", psave, lno));

	/* get the end mark '}' */

	end = read_code_block(p);

	_skip_spaces(end);

	/* ; -> next_line */
	end++;
	
	p++;

	code = strndup(p, end - p - 1);

	/* now let's do something interesting */

	*res = iini_run(interp, code);

	free(code);
	free(interp);

	_skip_spaces(end);

	/* this, parse_line will not bark. */
	line_buffer = end;

	return 1;
}



